/**
 * BeanMapConverter.java
 */
package com.weasel.helper;

import static java.util.stream.Collectors.*;

import com.google.common.collect.Maps;
import com.weasel.core.annotation.FieldAlias;
import com.weasel.core.exception.TransformException;
import com.weasel.core.helper.GodHands;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.apache.commons.lang.StringUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;

/**
 * map类型与javaBean之间的转换
 * 
 * @author Dylan
 * @time 2013-3-16
 */
public class BeanMapConverter {

	private static PropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean();

	/**
	 * map类型转换成javaBean
	 * 
	 * @param properties
	 *            保存bean属性和值的map
	 * @param beanClass
	 *            转换成bean的大型
	 * @return
	 */
	public static <T> T mapToBean(Map<String, Object> properties, Class<T> beanClass) {
		T bean = null;
		try {
			bean = beanClass.newInstance();
			BeanUtils.populate(bean, properties);
		} catch (InstantiationException e) {
			throw new TransformException(e.getMessage());
		} catch (IllegalAccessException e) {
			throw new TransformException(e.getMessage());
		} catch (InvocationTargetException e) {
			throw new TransformException(e.getMessage());
		}
		return bean;
	}
	

	/**
	 * javaBean 转换成 map类型
	 * 
	 * @param bean
	 *            实体bean
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static Map<String,Object> beanToMap(Object bean) {
		try {
			return BeanUtils.describe(bean);
		} catch (Exception e) {
			throw new TransformException(e.getMessage());
		}

	}
	
	/**
	 * 
	 * @param source 源对象
	 * @param target 目标对象的class
	 * @param fieldMapping 该map是设定源对象的属性和目标对象的属性映射关系，key为源对象的属性，value为目录对象的属性
	 * @return
	 */
	public static <T> T beanToBean(Object source,Class<T> target,final Map<String,String> fieldMapping){
		
		Map<String,Object> sourceMap = beanToMap(source);
		
		Map<String,Object> filtedMap = fieldMapping.keySet()
												   .stream()
												   .filter(fieldName -> sourceMap.containsKey(fieldName))
												   .collect(toMap(fieldName -> fieldMapping.get(fieldName), fieldName -> sourceMap.get(fieldName)));
		return mapToBean(filtedMap, target);
	}

	/**将值不为null的属性映射到map中，key为属性名，value为属性值
	 * @param bean
	 * @return
	 */
	public static Map<String, Object> notNullFieldMap(Object bean) {

		PropertyDescriptor[] descriptors = getPropertyDescriptors(bean);
		Map<String, Object> beanMap = ArrayHelper.arrayToList(descriptors).stream()
																		  .filter(descriptor -> {
																			  try {
																				  return !StringUtils.equals("class", descriptor.getName())
																						  && (getPropertyUtilsBean().getReadMethod(descriptor)) != null
																						  && (getPropertyUtilsBean().getProperty(bean, descriptor.getName()) != null);
																			  } catch (Exception e) {
																				  throw new RuntimeException(e.getMessage());
																			  }
																		  }).collect(toMap(descriptor -> descriptor.getName(), descriptor -> {
					try {
						return getPropertyUtilsBean().getProperty(bean, descriptor.getName());
					} catch (Exception e) {
						throw new RuntimeException(e.getMessage());
					}
				}));

		return beanMap;
	}

	/**将带有FieldAlias注解的属性映射到map中，如果没有该注解，就用原属性名作为key，如果有，就用注解定义的别名作为key,value为属性的值
	 * @param bean
	 * @return
	 */
	public static Map<String, Object> withAliasMap(Object bean) {

		PropertyDescriptor[] descriptors = getPropertyDescriptors(bean);
		Map<String, Object> beanMap = ArrayHelper.arrayToList(descriptors).stream()
																		  .filter(descriptor -> {
																			  try {
																				  return !StringUtils.equals("class", descriptor.getName())
                                                                                          && getPropertyUtilsBean().getReadMethod(descriptor) != null
                                                                                          && getPropertyUtilsBean().getProperty(bean, descriptor.getName()) != null;
																			  } catch (Exception e) {
																				  throw new RuntimeException(e.getMessage());
																			  }
																		  }).collect(toMap(descriptor -> {
																			    String name =  descriptor.getName();
																				FieldAlias fieldAlias = null;
																				Field field = GodHands.getAccessibleField(bean, name);
																				if (null != field && null != (fieldAlias = field.getAnnotation(FieldAlias.class))) {
																					name = fieldAlias.value();
																				}
					                                                            return name;
																			},descriptor -> {
																				try {
																					return getPropertyUtilsBean().getProperty(bean, descriptor.getName());
																				} catch (Exception e) {
																					throw new RuntimeException(e.getMessage());
																				}
																			}));
		return beanMap;
	}

	private static PropertyDescriptor[] getPropertyDescriptors(Object bean) {
		PropertyUtilsBean propertyUtilsBean = getPropertyUtilsBean();
		return propertyUtilsBean.getPropertyDescriptors(bean);
	}

	private static PropertyUtilsBean getPropertyUtilsBean() {
		return propertyUtilsBean;
	}

}
